﻿using System;
using dotnetcdf;

namespace Faml.Operations
{
    [OperationContainer]
    public static class netCDFOperations
    {
        #region 1d

        [OperationName("netcdf")]
        public static double[] netcdf(string url, string var)
        {
            return netcdf<double>(url, var);
        }

        [OperationName("netcdf_i")]
        public static int[] netcdf_i(string url, string var)
        {
            return netcdf<int>(url, var);
        }
        
        public static T[] netcdf<T>(string url, string var)
        {
            int ncid = 0, varid = 0;
            int res, ndimsp = 0;

            res = NetCDF.nc_open(ref url, (int)NetCDF.cmode.NC_NOWRITE, ref ncid);
            if (res != 0)
                throw new Exception("Cannot open NetCDF file");

            try
            {
                res = NetCDF.nc_inq_varid(ncid, ref var, ref varid);
                if (res != 0)
                    throw new Exception("Cannot read NetCDF file");

                res = NetCDF.nc_inq_varndims(ncid, varid, ref ndimsp);
                if (res != 0)
                    throw new Exception("Cannot read NetCDF file");

                if (ndimsp != 1)
                    throw new Exception("Expected for one-dimensional array");

                int[] dimids = new int[1];
                int natts = 0;
                res = NetCDF.nc_inq_vardimid(ncid, varid, dimids, ref natts);
                if (res != 0)
                    throw new Exception("Cannot read NetCDF file");


                T[] array = null;

                int dimlen = 0;
                NetCDF.nc_inq_dimlen(ncid, dimids[0], ref dimlen);

                if (typeof(T) == typeof(int))
                {
                    int[] intarr = new int[dimlen];
                    res = NetCDF.nc_get_var_int(ncid, varid, intarr);
                    array = (T[])((Array)intarr);
                }
                else if (typeof(T) == typeof(double))
                {
                    double[] doublearr = new double[dimlen];
                    res = NetCDF.nc_get_var_double(ncid, varid, doublearr);
                    array = (T[])((Array)doublearr);
                }
                else
                {
                    throw new Exception("Type is not supported");
                }

                if (res != 0)
                    throw new Exception("Variable not found");

                return array;
            }
            finally
            {
                NetCDF.nc_close(ncid);
            }
        }

        #endregion

        #region 2d
        [OperationName("netcdf2d")]
        public static double[,] netcdf2d(string url, string var)
        {
            return netcdf2d<double>(url, var);
        }

        [OperationName("netcdf2d_i")]
        public static int[,] netcdf2d_i(string url, string var)
        {
            return netcdf2d<int>(url, var);
        }

        public static T[,] netcdf2d<T>(string url, string var)
        {
            int ncid = 0, varid = 0;
            int res, ndimsp = 0;

            res = NetCDF.nc_open(ref url, (int)NetCDF.cmode.NC_NOWRITE, ref ncid);
            if (res != 0)
                throw new Exception("Cannot open NetCDF file");

            try
            {
                res = NetCDF.nc_inq_varid(ncid, ref var, ref varid);
                if (res != 0)
                    throw new Exception("Cannot open NetCDF file");

                res = NetCDF.nc_inq_varndims(ncid, varid, ref ndimsp);
                if (res != 0)
                    throw new Exception("Cannot open NetCDF file");

                if (ndimsp != 2)
                    throw new Exception("Expected for two-dimensional array");

                int[] dimids = new int[2];
                int natts = 0;
                res = NetCDF.nc_inq_vardimid(ncid, varid, dimids, ref natts);
                if (res != 0)
                    throw new Exception("Cannot open NetCDF file");

                int width = 0, height = 0;
                NetCDF.nc_inq_dimlen(ncid, dimids[0], ref width);
                NetCDF.nc_inq_dimlen(ncid, dimids[1], ref height);

                T[,] array = null;

                if (typeof(T) == typeof(int))
                {
                    int[,] intarr2d = new int[width, height];
                    int[] intarr = new int[width * height];
                    res = NetCDF.nc_get_var_int(ncid, varid, intarr);

                    int ih = 0;
                    for (int i = 0; i < width; i++, ih += height)
                    {
                        for (int j = 0; j < height; j++)
                        {
                            intarr2d[i, j] = intarr[ih + j];
                        }
                    }

                    array = (T[,])((Array)intarr2d);
                }
                else if (typeof(T) == typeof(double))
                {
                    double[,] doublearr2d = new double[width, height];
                    double[] doublearr = new double[width * height];
                    res = NetCDF.nc_get_var_double(ncid, varid, doublearr);

                    int ih = 0;
                    for (int i = 0; i < width; i++, ih += height)
                    {
                        for (int j = 0; j < height; j++)
                        {
                            doublearr2d[i, j] = doublearr[ih + j];
                        }
                    }

                    array = (T[,])((Array)doublearr2d);
                }
                else
                {
                    throw new Exception("Type is not supported");
                }

                if (res != 0)
                    throw new Exception("Variable not found");

                NetCDF.nc_close(ncid);

                return array;
            }
            finally
            {
                NetCDF.nc_close(ncid);
            }
        }

        #endregion 
    
        #region 3d
        [OperationName("netcdf3d")]
        public static double[,,] netcdf3d(string url, string var)
        {
            return netcdf3d<double>(url, var);
        }

        [OperationName("netcdf3d_i")]
        public static int[,,] netcdf3d_i(string url, string var)
        {
            return netcdf3d<int>(url, var);
        }

        [OperationName("netcdf3d_s")]
        public static short[, ,] netcdf3d_s(string url, string var)
        {
            return netcdf3d<short>(url, var);
        }

        public static T[,,] netcdf3d<T>(string url, string var)
        {
            int ncid = 0, varid = 0;
            int res, ndimsp = 0;

            res = NetCDF.nc_open(ref url, (int)NetCDF.cmode.NC_NOWRITE, ref ncid);
            if (res != 0)
                throw new Exception("Cannot open NetCDF file");

            try
            {
                res = NetCDF.nc_inq_varid(ncid, ref var, ref varid);
                if (res != 0)
                    throw new Exception("Cannot open NetCDF file");

                res = NetCDF.nc_inq_varndims(ncid, varid, ref ndimsp);
                if (res != 0)
                    throw new Exception("Cannot open NetCDF file");

                if (ndimsp != 3)
                    throw new Exception("Expected for three-dimensional array");

                int[] dimids = new int[3];
                int natts = 0;
                res = NetCDF.nc_inq_vardimid(ncid, varid, dimids, ref natts);
                if (res != 0)
                    throw new Exception("Cannot open NetCDF file");

                int width = 0, height = 0, depth = 0;
                NetCDF.nc_inq_dimlen(ncid, dimids[0], ref width);
                NetCDF.nc_inq_dimlen(ncid, dimids[1], ref height);
                NetCDF.nc_inq_dimlen(ncid, dimids[2], ref depth);

                T[,,] array = null;

                if (typeof(T) == typeof(int))
                {
                    int[,,] intarr3d = new int[width, height, depth];
                    int[] intarr = new int[width * height * depth];
                    res = NetCDF.nc_get_var_int(ncid, varid, intarr);

                    int index = 0;
                    for (int i = 0; i < width; i++)
                    {
                        for (int j = 0; j < height; j++)
                        {
                            for (int k = 0; k < depth; k++)
                            {
                                intarr3d[i, j, k] = intarr[index++];
                            }
                        }
                    }

                    array = (T[,,])((Array)intarr3d);
                }
                else if (typeof(T) == typeof(short))
                {
                    short[, ,] intarr3d = new short[width, height, depth];
                    short[] intarr = new short[width * height * depth];
                    res = NetCDF.nc_get_var_short(ncid, varid, intarr);

                    int index = 0;
                    for (int i = 0; i < width; i++)
                    {
                        for (int j = 0; j < height; j++)
                        {
                            for (int k = 0; k < depth; k++)
                            {
                                intarr3d[i, j, k] = intarr[index++];
                            }
                        }
                    }

                    array = (T[, ,])((Array)intarr3d);
                }
                else if (typeof(T) == typeof(double))
                {
                    double[,,] doublearr3d = new double[width, height, depth];
                    double[] doublearr = new double[width * height * depth];
                    res = NetCDF.nc_get_var_double(ncid, varid, doublearr);

                    int index = 0;
                    for (int i = 0; i < width; i++)
                    {
                        for (int j = 0; j < height; j++)
                        {
                            for (int k = 0; k < depth; k++)
                            {
                                doublearr3d[i, j, k] = doublearr[index++];
                            }
                        }
                    }

                    array = (T[,,])((Array)doublearr3d);
                }
                else
                {
                    throw new Exception("Type is not supported");
                }

                if (res != 0)
                    throw new Exception("Variable not found");

                NetCDF.nc_close(ncid);

                return array;
            }
            finally
            {
                NetCDF.nc_close(ncid);
            }
        }

        #endregion

        #region Time

        [OperationName("netcdf_time")]
        public static DateTime time(double hourTicks)
        {
            int hours = (int)Math.Floor(hourTicks);

            double dmin = (hourTicks - hours) * 60;
            int minutes = (int)Math.Floor(dmin);

            double dsec = (dmin - minutes) * 60;
            int sec = (int)Math.Floor(dsec);

            int msec = (int)Math.Floor((dsec - sec) * 1000);

            return new DateTime(1, 1, 1, 0, 0, 0) + new TimeSpan(0, hours, minutes, sec, msec);
        }

        [OperationName("netcdf_timeToTicks")]
        public static double timeToTicks(DateTime dateTime)
        {
            return dateTime.Subtract(new DateTime(1, 1, 1, 0, 0, 0)).TotalHours;
        }

        #endregion
    }
}
